/**
 * The MIT License (MIT)
 *
 * Copyright (c) 2021 Marcus Britanicus (https://gitlab.com/marcusbritanicus)
 * Copyright (c) 2021 Abrar (https://gitlab.com/s96Abrar)
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 **/

#include <QObject>
#include <QDebug>
#include <QImage>
#include <QThread>
#include <QFile>
#include <QCoreApplication>

#include <typeinfo>
#include <iostream>

#include <wayland-client.h>
#include <unistd.h>
#include <signal.h>

#include <wayqt/WayQtUtils.hpp>
#include <wayqt/Registry.hpp>
#include <wayqt/DataControl.hpp>
#include <wlr-data-control-unstable-v1-client-protocol.h>

#include "DataControlImpl.hpp"

/**
 * MimeData
 */

WQt::MimeData::MimeData() {
    /** Nothing to be done */
}


WQt::MimeData::MimeData( const WQt::MimeData& other ) {
    /** Copy the data from @other */
    mimeDataMap = other.mimeDataMap;
}


QStringList WQt::MimeData::formats() {
    return mimeDataMap.keys();
}


QByteArray WQt::MimeData::data( QString format ) {
    return mimeDataMap.value( format );
}


void WQt::MimeData::setData( QString format, QByteArray data ) {
    /** Remove the format if data is empty */
    if ( not data.length() ) {
        if ( mimeDataMap.contains( format ) ) {
            mimeDataMap.remove( format );
        }

        return;
    }

    /** Store (format, data) pair */
    mimeDataMap[ format ] = data;
}


void WQt::MimeData::clear() {
    mimeDataMap.clear();
}


QMimeData * WQt::MimeData::toQMimeData() {
    QMimeData *mdata = new QMimeData();

    for ( QString mType: mimeDataMap.keys() ) {
        mdata->setData( mType, mimeDataMap.value( mType ) );
    }

    return mdata;
}


WQt::MimeData& WQt::MimeData::operator=( const WQt::MimeData& other ) {
    /** Copy the data from @other */
    mimeDataMap = other.mimeDataMap;
    return *this;
}


bool WQt::MimeData::operator==( const WQt::MimeData& other ) {
    return (mimeDataMap == other.mimeDataMap);
}


/**
 * Data Control Manager
 */

WQt::DataControlManager::DataControlManager( zwlr_data_control_manager_v1 *mgr ) {
    mObj = mgr;
}


WQt::DataControlManager::~DataControlManager() {
    zwlr_data_control_manager_v1_destroy( mObj );
}


WQt::DataControlSource *WQt::DataControlManager::createDataSource() {
    zwlr_data_control_source_v1 *src = zwlr_data_control_manager_v1_create_data_source( mObj );

    return new WQt::DataControlSource( src );
}


WQt::DataControlDevice *WQt::DataControlManager::getDataDevice( wl_seat *seat ) {
    zwlr_data_control_device_v1 *dev = zwlr_data_control_manager_v1_get_data_device( mObj, seat );

    return new WQt::DataControlDevice( dev );
}


zwlr_data_control_manager_v1 *WQt::DataControlManager::get() {
    return mObj;
}


/**
 * Data Control Device
 */

WQt::DataControlDevice::DataControlDevice( zwlr_data_control_device_v1 *dev ) {
    mObj = dev;

    /**
     * Init the currentOffer object, so that this c++ object is never null
     * Note that while @currentOffer is not null, it's not valid until an
     * update(...) is called with a valid wlr_data_control_offer pointer.
     */
    currentOffer = new DataControlOffer();
}


WQt::DataControlDevice::~DataControlDevice() {
    zwlr_data_control_device_v1_destroy( mObj );
    mObj = nullptr;

    /** Start listening to the events of wlr_data_control_device */
    if ( wl_proxy_get_listener( (wl_proxy *)mObj ) != &mListener ) {
        zwlr_data_control_device_v1_add_listener( mObj, &mListener, this );
    }
}


void WQt::DataControlDevice::setup() {
    if ( mIsSetup == false ) {
        mIsSetup = true;

        if ( pendingOffer ) {
            if ( isPrimary ) {
                emit primarySelectionOffered( pendingOffer );
            }

            else {
                emit selectionOffered( pendingOffer );
            }

            currentOffer->update( pendingOffer->get() );
        }
    }
}


void WQt::DataControlDevice::setSelection( DataControlSource *src ) {
    /** Delete the current selection */
    if ( currentSelection ) {
        delete currentSelection;
    }

    /** Set the selection to @src */
    if ( src == nullptr ) {
        zwlr_data_control_device_v1_set_selection( mObj, nullptr );
    }

    else {
        /** Destroy the current selection */
        zwlr_data_control_device_v1_set_selection( mObj, src->get() );
    }

    currentSelection = src;
}


void WQt::DataControlDevice::setPrimarySelection( DataControlSource *src ) {
    /** Delete the current primary */
    if ( currentPrimary ) {
        delete currentPrimary;
    }

    /** Set the primary_selection to @src */
    if ( src == nullptr ) {
        zwlr_data_control_device_v1_set_primary_selection( mObj, nullptr );
    }

    else {
        zwlr_data_control_device_v1_set_primary_selection( mObj, src->get() );
    }

    currentPrimary = src;
}


zwlr_data_control_device_v1 *WQt::DataControlDevice::get() {
    return mObj;
}


void WQt::DataControlDevice::handleDataOffer( void *data, struct zwlr_data_control_device_v1 *, struct zwlr_data_control_offer_v1 *offer ) {
    DataControlDevice *dev = reinterpret_cast<DataControlDevice *>(data);

    /**
     * Always invalidate an existing data offer. Invalidation is
     * performed only if the internal @mObj pointer is valid.
     * This ensures that the client listening to DataControlDevice
     * will reset its connections and will not read spurious data.
     */
    dev->currentOffer->invalidate();

    /**
     * If the new offer is not a nullptr, update the offer.
     * Since the clients have already disconnected at this point,
     * they will not yet get info about mimetypes offered here.
     */
    if ( offer != nullptr ) {
        dev->currentOffer->update( offer );
    }
}


void WQt::DataControlDevice::handleSelection( void *data, struct zwlr_data_control_device_v1 *, struct zwlr_data_control_offer_v1 *offer ) {
    DataControlDevice *dev = reinterpret_cast<DataControlDevice *>(data);

    /** A selection event with null data offer resets the offer. */
    if ( offer == nullptr ) {
        dev->currentOffer->invalidate();
    }

    /**
     * Update the data offer - this _should_ be same as the one received
     * in the "data offer" event.
     * Send out a selectionOffered event. This will enable the client to
     * reconnect to DataControlOffer events.
     */
    else {
        dev->currentOffer->update( offer );

        /**
         * Ideally, we should emit this only if it's not been emitted
         * for this data offer. Use a flag?
         */
        emit dev->selectionOffered( dev->currentOffer );
    }
}


void WQt::DataControlDevice::handlePrimarySelection( void *data, struct zwlr_data_control_device_v1 *, struct zwlr_data_control_offer_v1 *offer ) {
    DataControlDevice *dev = reinterpret_cast<DataControlDevice *>(data);

    /** A selection event with null data offer resets the offer. */
    if ( offer == nullptr ) {
        dev->currentOffer->invalidate();
    }

    /**
     * Update the data offer - this _should_ be same as the one received
     * in the "data offer" event.
     * Send out a selectionOffered event. This will enable the client to
     * reconnect to DataControlOffer events.
     */
    else {
        dev->currentOffer->update( offer );

        /**
         * Ideally, we should emit this only if it's not been emitted
         * for this data offer. Use a flag?
         */
        emit dev->primarySelectionOffered( dev->currentOffer );
    }
}


void WQt::DataControlDevice::handleFinished( void *data, struct zwlr_data_control_device_v1 * ) {
    /** Every thing is done, and delete the DataControlDevice */
    DataControlDevice *dev = reinterpret_cast<DataControlDevice *>(data);

    zwlr_data_control_device_v1_destroy( dev->mObj );
    dev->mObj = nullptr;
}


const zwlr_data_control_device_v1_listener WQt::DataControlDevice::mListener = {
    handleDataOffer,
    handleSelection,
    handleFinished,
    handlePrimarySelection
};

/**
 * Data Control Source
 */

WQt::DataControlSource::DataControlSource( zwlr_data_control_source_v1 *src ) {
    mObj = src;

    if ( wl_proxy_get_listener( (wl_proxy *)mObj ) != &mListener ) {
        zwlr_data_control_source_v1_add_listener( mObj, &mListener, this );
    }

    mMimeData.clear();

    writer = new PipeWriter();
}


WQt::DataControlSource::~DataControlSource() {
    zwlr_data_control_source_v1_destroy( mObj );
}


void WQt::DataControlSource::setSelectionData( WQt::MimeData mData ) {
    mMimeData = mData;
}


void WQt::DataControlSource::offer( QString mimeType ) {
    zwlr_data_control_source_v1_offer( mObj, mimeType.toUtf8().constData() );
}


zwlr_data_control_source_v1 *WQt::DataControlSource::get() {
    return mObj;
}


void WQt::DataControlSource::handleSend( void *data, struct zwlr_data_control_source_v1 *, const char *mimeType, int32_t fd ) {
    DataControlSource *dev = reinterpret_cast<DataControlSource *>(data);
    emit dev->dataRequested( QString::fromUtf8( mimeType ), fd );

    dev->writer->writeData( fd, dev->mMimeData.data( mimeType ) );
}


void WQt::DataControlSource::handleCanceled( void *data, struct zwlr_data_control_source_v1 * ) {
    DataControlSource *dev = reinterpret_cast<DataControlSource *>(data);
    emit dev->canceled();

    zwlr_data_control_source_v1_destroy( dev->mObj );
    dev->mObj = nullptr;

    dev->mMimeData.clear();
}


const zwlr_data_control_source_v1_listener WQt::DataControlSource::mListener = {
    handleSend,
    handleCanceled
};

/**
 * Data Control Offer
 */

WQt::DataControlOffer::DataControlOffer() {
    mObj = nullptr;
    mMimeTypes.clear();

    reader = new PipeReader();
}


WQt::DataControlOffer::~DataControlOffer() {
    zwlr_data_control_offer_v1_destroy( mObj );
}


bool WQt::DataControlOffer::update( zwlr_data_control_offer_v1 *offer ) {
    if ( mObj == offer ) {
        return false;
    }

    mMimeTypes.clear();

    /** We can destroy an object only if it's not null */
    if ( mObj != nullptr ) {
        zwlr_data_control_offer_v1_destroy( mObj );
    }

    if ( offer != nullptr ) {
        mObj = offer;

        if ( wl_proxy_get_listener( (wl_proxy *)mObj ) != &mListener ) {
            zwlr_data_control_offer_v1_add_listener( mObj, &mListener, this );
        }
    }

    return true;
}


void WQt::DataControlOffer::invalidate() {
    /** Perform an invalidation - destroying the internal pointer - only if it's valid */
    if ( mObj != nullptr ) {
        zwlr_data_control_offer_v1_destroy( mObj );
        mObj = nullptr;

        emit invalidated();
    }

    /** Always reset the mimeTypes */
    mMimeTypes.clear();
}


bool WQt::DataControlOffer::isValid() {
    /** This object is valid as long as the internal object is valid */
    return (mObj != nullptr);
}


QByteArray WQt::DataControlOffer::retrieveData( QString mimeType ) {
    if ( mObj == nullptr ) {
        return QByteArray();
    }

    if ( not mMimeTypes.contains( mimeType ) ) {
        qCritical() << "[ERROR]: Source does not offer the requested mimeType:" << mimeType;
        return QByteArray();
    }

    int pipeFds[ 2 ];

    if ( pipe( pipeFds ) != 0 ) {
        qCritical() << "[ERROR]: Failed to create pipe to retrieve data.";
        return QByteArray();
    }

    /** Request */
    zwlr_data_control_offer_v1_receive( mObj, mimeType.toUtf8().constData(), pipeFds[ 1 ] );

    wl_display_flush( WQt::Wayland::display() );

    close( pipeFds[ 1 ] );

    wl_display_flush( WQt::Wayland::display() );

    reader->readFromPipe( pipeFds[ 0 ] );
    while ( reader->isRunning() ) {
        QCoreApplication::processEvents();
    }

    return reader->data();
}


QStringList WQt::DataControlOffer::offeredMimeTypes() {
    return mMimeTypes;
}


zwlr_data_control_offer_v1 *WQt::DataControlOffer::get() {
    return mObj;
}


void WQt::DataControlOffer::handleOffer( void *data, struct zwlr_data_control_offer_v1 *, const char *mimeType ) {
    /** Capture all the offered mimeTypes. */
    DataControlOffer *offer = reinterpret_cast<DataControlOffer *>(data);

    if ( not offer->isValid() ) {
        return;
    }

    QString mt( mimeType );

    /** Advertise this mimeType only if it's not been done before */
    if ( not offer->mMimeTypes.contains( mt ) ) {
        offer->mMimeTypes << mt;
        emit offer->mimeTypeOffered( mt );
    }
}


const zwlr_data_control_offer_v1_listener WQt::DataControlOffer::mListener = {
    handleOffer
};
